package com.nononsenseapps.feeder.util

import com.nononsenseapps.feeder.db.entity.FeedItem
import com.nononsenseapps.jsonfeed.Attachment
import com.nononsenseapps.jsonfeed.Author
import com.nononsenseapps.jsonfeed.Feed
import com.nononsenseapps.jsonfeed.Item
import com.rometools.modules.mediarss.MediaEntryModule
import com.rometools.modules.mediarss.MediaModule
import com.rometools.rome.feed.synd.*
import org.joda.time.DateTime
import org.jsoup.parser.Parser.unescapeEntities
import java.net.URL


fun SyndFeed.asFeed(baseUrl: URL): Feed {
    val feedAuthor: Author? = this.authors?.firstOrNull()?.asAuthor()

    return Feed(
            title = plainTitle(),
            home_page_url = relativeLinkIntoAbsoluteOrNull(
                    baseUrl,
                    this.links?.firstOrNull {
                        "alternate" == it.rel && "text/html" == it.type
                    }?.href ?: this.link
            ),
            feed_url = relativeLinkIntoAbsoluteOrNull(
                    baseUrl,
                    this.links?.firstOrNull { "self" == it.rel }?.href
            ),
            description = this.description,
            icon = this.image?.url,
            author = feedAuthor,
            items = this.entries?.map { it.asItem(baseUrl = baseUrl, feedAuthor = feedAuthor) }
    )
}

fun toSyndFeed(feed: com.nononsenseapps.feeder.db.entity.Feed, items: List<FeedItem>):SyndFeed{
    val syncFeed=SyndFeedImpl()
    syncFeed.title=feed.title
    syncFeed.link=feed.url.toString()
    syncFeed.encoding="utf-8"
    syncFeed.feedType="rss_1.0"
    syncFeed.description="收藏"
    syncFeed.entries=items.map { toSyndEntry(feed,it) }
    return syncFeed
}

fun toSyndEntry(feed: com.nononsenseapps.feeder.db.entity.Feed, feedItem: FeedItem):SyndEntry{
    val syncEntry=SyndEntryImpl()
    syncEntry.title=feedItem.title
    syncEntry.link=feedItem.link
    syncEntry.publishedDate=feedItem.createDate.toDate()
    syncEntry.author=feedItem.author?:""
    syncEntry.contents= listOf(SyndContentImpl().apply {
        this.type="text/html"
        this.value=feedItem.description
    })
    feedItem.enclosureLink?.let{
        syncEntry.enclosures= listOf(SyndEnclosureImpl().apply {
            this.url=feedItem.enclosureLink
        })
    }
    return syncEntry
}

fun com.nononsenseapps.feeder.db.entity.Feed.toJsonFeed(items:List<Item>?):Feed=Feed(
        title=this.title,
        feed_url = this.url.let { it.toString() },
        author = items?.let { items[0].author },
        items = items
)

fun SyndEntry.asItem(baseUrl: URL, feedAuthor: Author? = null): Item {
    val contentHtml=this.contentHtml()
    return Item(
            id = relativeLinkIntoAbsoluteOrNull(baseUrl, this.uri),
            url = this.linkToHtml(baseUrl),
            title = plainTitle(),
            content_text = contentText()?:mediaDescription()?:"",
            content_html = contentHtml,
            image = this.thumbnail(baseUrl),
            date_published = this.publishedRFC3339Date(),
            date_modified = this.modifiedRFC3339Date(),
            author = this.authors?.firstOrNull()?.asAuthor() ?: feedAuthor,
            attachments = this.enclosures?.map { it.asAttachment(baseUrl = baseUrl)},
            unfold = true,
            tags  = this.categories?.map { it.name }
    )
}

val htmlRegex=Regex("<[^>]*>|\\&#\\d*;|\\&\\w*;|\\s{3}|\\r|\\n")

fun Item.unfold(){
    this.content_html?.let{possiblyHtml->
        possiblyHtml.startsWith("""<![CDATA[""").isTrue {
            this.content_html=possiblyHtml.substring("""<![CDATA[""".length)
            possiblyHtml.endsWith("""]]>""").isTrue {
                this.content_html=this.content_html!!.substring(0,content_html!!.length-"""]]>""".length)
            }
        }
    }

    this.summary=this.content_text
}

fun contentText(possiblyHtml:String):String{
    val result = htmlToPlainConverter().convert(possiblyHtml)

    // Description consists of at least one image, avoid opening browser for this item
    return when {
        result.isBlank() && possiblyHtml.contains("img")-> "<image>"
        else -> result
    }
}

fun thumbnail(htmlContext:String,feedBaseUrl: String?):String?{
    val imgLink: String? = naiveFindImageLink(htmlContext)
    // Now we are resolving against original, not the feed
    if(feedBaseUrl != null && imgLink != null)
        return relativeLinkIntoAbsolute(URL(feedBaseUrl), imgLink)
    return imgLink
}

fun String.orIfBlank(block: () -> String): String =
        when (this.isBlank()) {
            true -> block()
            false -> this
        }

/**
 * Returns an absolute link, or null
 */
fun SyndEntry.linkToHtml(feedBaseUrl: URL): String? {
    this.links?.firstOrNull { "alternate" == it.rel && "text/html" == it.type }?.let {
        return relativeLinkIntoAbsoluteOrNull(feedBaseUrl, it.href)
    }

    this.links?.firstOrNull { "self" == it.rel && "text/html" == it.type }?.let {
        return relativeLinkIntoAbsoluteOrNull(feedBaseUrl, it.href)
    }

    this.links?.firstOrNull { "self" == it.rel }?.let {
        return relativeLinkIntoAbsoluteOrNull(feedBaseUrl, it.href)
    }

    this.link?.let {
        return relativeLinkIntoAbsoluteOrNull(feedBaseUrl, it)
    }

    this.uri?.let {
        return relativeLinkIntoAbsoluteOrNull(feedBaseUrl, it)
    }

    return null
}


fun SyndEnclosure.asAttachment(baseUrl: URL): Attachment {
    return Attachment(
            url = relativeLinkIntoAbsoluteOrNull(
                    baseUrl,
                    this.url
            ),
            mime_type = this.type,
            size_in_bytes = this.length
    )
}

fun SyndPerson.asAuthor(): Author {
    val url: String? = when {
        this.uri != null -> this.uri
        this.email != null -> "mailto:${this.email}"
        else -> null
    }
    return Author(name = this.name,
            url = url)
}


fun SyndEntry.contentText(): String? {
    var selectString: String? = null
    if( contents != null && contents.isNotEmpty())  { // Atom
            for (c in contents) {
                if ("text" == c.type && c.value != null) {
                    return c.value
                } else if (null == c.type && c.value != null) {
                    // Suspect it might be text as per the Rome docs
                    // https://github.com/ralph-tice/rome/blob/master/src/main/java/com/sun/syndication/feed/synd/SyndContent.java
                    selectString = c.value
                    break
                }
            }
     }
    // Description consists of at least one image, avoid opening browser for this item
    return selectString
}


private fun convertAtomContentToPlainText(content: SyndContent?, fallback: String?): String {
    return htmlToPlainConverter().convert(possiblyHtmlFromContent(content, fallback))
}

private fun possiblyHtmlFromContent(content: SyndContent?, fallback: String?): String =
        when (content?.type == "html") {
            true -> unescapeEntities(content!!.value, true)
            false -> content?.value ?: fallback
        } ?: ""

fun SyndFeed.plainTitle(): String = convertAtomContentToPlainText(titleEx, title)
fun SyndEntry.plainTitle(): String = convertAtomContentToPlainText(titleEx, title)

fun SyndEntry.contentHtml(): String {
    var possiblyHtml: String? = contents?.filter {
        when (it.type) {
            "xhtml", "html" -> true
            else -> false
        }
    }?.take(1)?.map {
        when (it.type) {
            "html" -> unescapeEntities(it.value, true)
            else -> it.value
        }
    }?.firstOrNull()

    if (possiblyHtml == null) {
        possiblyHtml = contents?.firstOrNull()?.value
    }

    if (possiblyHtml == null) {
        possiblyHtml = description?.value
    }

    return possiblyHtml?: ""
}

fun findImageLinkInEnclosures(enclosures: List<SyndEnclosure?>?): String? {
    if (enclosures != null) {
        for (enclosure in enclosures) {
            if (enclosure != null) {
                if (enclosure.type != null && enclosure.url != null && enclosure.type.startsWith("image/")) {
                    return enclosure.url
                }
            }
        }
    }
    return null
}

fun SyndEntry.mediaDescription(): String? {
    val media = this.getModule(MediaModule.URI) as MediaEntryModule?

    return media?.metadata?.description
            ?: media?.mediaContents?.firstOrNull { it.metadata?.description?.isNotBlank() == true }?.metadata?.description
            ?: media?.mediaGroups?.firstOrNull { it.metadata?.description?.isNotBlank() == true }?.metadata?.description
}

/**
 * Returns an absolute link, or null
 */
fun SyndEntry.thumbnail(feedBaseUrl: URL): String? {
    val media = this.getModule(MediaModule.URI) as MediaEntryModule?

    val thumbnail: String? = media?.metadata?.thumbnail?.firstOrNull()?.url?.toString()
            ?: media?.mediaContents?.firstOrNull { "image" == it.medium }?.reference?.toString()
            ?: media?.mediaGroups?.mapNotNull { it.metadata?.thumbnail?.firstOrNull() }?.firstOrNull()?.url?.toString()
            ?: enclosures?.asSequence()
                    ?.filterNotNull()
                    ?.filter { it.type?.startsWith("image/") == true }
                    ?.mapNotNull { it.url }
                    ?.firstOrNull()

    return thumbnail?.let{ relativeLinkIntoAbsolute(feedBaseUrl, thumbnail) }

    /*return when {
        thumbnail != null -> relativeLinkIntoAbsolute(feedBaseUrl, thumbnail)
        else -> {
            val imgLink: String? = naiveFindImageLink(htmlContext)
            // Now we are resolving against original, not the feed
            val siteBaseUrl: String? = this.linkToHtml(feedBaseUrl)

            when {
                siteBaseUrl != null && imgLink != null -> relativeLinkIntoAbsolute(URL(siteBaseUrl), imgLink)
                imgLink != null -> relativeLinkIntoAbsolute(feedBaseUrl, imgLink)
                else -> null
            }
        }
    }*/
}

fun imageCount(html:String?): Int? {
    return naiveFindImageLinkCount(html)
}

fun SyndEntry.publishedRFC3339Date(): String? =
        when (publishedDate != null) {
            true -> DateTime(publishedDate.time).toDateTimeISO().toString()
            else -> modifiedRFC3339Date() // This is the required element in atom feeds so it is a good fallback
        }

fun SyndEntry.modifiedRFC3339Date(): String? =
        when (updatedDate != null) {
            true -> DateTime(updatedDate.time).toDateTimeISO().toString()
            else -> null
        }

fun htmlToPlainConverter()= HtmlToPlainTextConverter()

